/**
 *    Copyright 2012-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.stlm2.core.memcached;

import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactory;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.auth.PlainCallbackHandler;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.ibatis.cache.CacheException;
import org.w3c.dom.Element;

import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author Simone Tripodi
 */
public final class MemcachedClient {

    private static net.spy.memcached.MemcachedClient client;

    // 服务器的地址
    private static String servers;

    // memcached整体的过期时间，单位：秒
    private static Integer expiration = 86400 * 30;

    // 是否启用异步获取数据
    private static Boolean isUsingAsyncGet = false;

    // 异步获取数据的超时时间，豪秒
    private static Long timeout = 500L;

    // 是否在存数据前进行GZIP压缩
    private static boolean isCompressionEnabled = false;

    // 用户名
    private static String username;

    // 密码
    private static String password;

    // 是否启用缓存
    private static Boolean isUsingCache = false;

    public static void parse(Element ele) {
        if(ele == null)
            return ;

        servers = ele.getAttribute("servers");
        username = ele.getAttribute("username");
        password = ele.getAttribute("password");

        String expirationStr = ele.getAttribute("expiration");
        if (StringUtils.isNotEmpty(expirationStr))
            expiration = NumberUtils.toInt(expirationStr, expiration);

        String isUsingAsyncGetStr = ele.getAttribute("asyncget");
        if (StringUtils.isNotEmpty(isUsingAsyncGetStr))
            isUsingAsyncGet = BooleanUtils.toBoolean(isUsingAsyncGetStr);

        String timeoutStr = ele.getAttribute("timeout");
        if (StringUtils.isNotEmpty(timeoutStr))
            timeout = NumberUtils.toLong(timeoutStr, timeout);

        String compressionStr = ele.getAttribute("compression");
        if (StringUtils.isNotEmpty(compressionStr))
            isCompressionEnabled = BooleanUtils.toBoolean(compressionStr);

        String isUsingCacheStr = ele.getAttribute("opencache");
        if (StringUtils.isNotEmpty(isUsingCacheStr))
            isUsingCache = BooleanUtils.toBoolean(isUsingCacheStr);

        try {
            if (StringUtils.isNotEmpty(servers) && isUsingCache) {
                System.out.println("open cache : " + isUsingCache);
                AuthDescriptor descriptor = (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) ? new AuthDescriptor(new String[]{"PLAIN"}, new PlainCallbackHandler(username, password)) : null;

                ConnectionFactory factory = new ConnectionFactoryBuilder()
                        .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
                        .setOpTimeout(100)
                        .setAuthDescriptor(descriptor)
                        .build();

                client = new net.spy.memcached.MemcachedClient(factory, AddrUtil.getAddresses(servers));
            }
        } catch (IOException e) {

        }
    }

    /**
     *
     * @param key
     * @return
     */
    public static Object getObject(String key) {
        Object ret = retrieve(key);
        return ret;
    }

    /**
     *
     *
     * @param keyString
     * @return
     * @throws Exception
     */
    private static Object retrieve(final String keyString) {
        if(!isUsingCache)
            return null;

        Object retrieved = null;
        try {
            if (isUsingAsyncGet) {
                Future<Object> future;
                if (isCompressionEnabled) {
                    future = client.asyncGet(keyString, new CompressorTranscoder());
                } else {
                    future = client.asyncGet(keyString);
                }

                try {
                    retrieved = future.get(timeout, TimeUnit.MILLISECONDS);
                } catch (Exception e) {
                    future.cancel(false);
                    throw new CacheException(e);
                }
            } else {
                if (isCompressionEnabled) {
                    retrieved = client.get(keyString, new CompressorTranscoder());
                } else {
                    retrieved = client.get(keyString);
                }
            }
        } catch (Exception e) {

        }

        return retrieved;
    }

    public static void putObject(String key, Object value, Integer expireTime) {
        storeInMemcached(key, value, expireTime);
    }

    /**
     * Stores an object identified by a key in Memcached.
     *
     * @param keyString the object key
     * @param value the object has to be stored.
     */
    private static void storeInMemcached(String keyString, Object value, Integer expireTime) {
        if (!isUsingCache)
            return;

        try {
            if (value != null
                    && !Serializable.class.isAssignableFrom(value.getClass())) {
                throw new CacheException("Object of type '"
                        + value.getClass().getName()
                        + "' that's non-serializable is not supported by Memcached");
            }

            expireTime = expireTime != null ? expireTime : expiration;//如果自定义过期时间就使用自定义过期时间，否则使用全局过期时间

            if (isCompressionEnabled) {
                client.set(keyString, expireTime, value, new CompressorTranscoder());
            } else {
                client.set(keyString, expireTime, value);
            }
        } catch (Exception e) {

        }
    }

    public static Object removeObject(String key) {
        if (!isUsingCache)
            return null;

        Object result = null;
        try {
            result = getObject(key);
            if (result != null) {
                client.delete(key);
            }
        } catch (Exception e) {

        }
        return result;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            client.shutdown(timeout, TimeUnit.MILLISECONDS);
            System.out.println("memchached client has bean shutdown");
        } catch (Exception e) {

        }
        super.finalize();
    }

}
